一步一步DIY zepto库,研究zepto源码7--动画模块(fx fx_method)

代码挂在我的github上,对应文件夹v0.7.1。
https://github.com/zrysmt/DIY-zepto

注:要在github源代码中自己编译的话,要在基础包命令:npm run dist上要进行扩展了,输入命令:

1
2
3
4
MODULES="zepto event fx fx_methods" npm run dist
# on Windows
> SET MODULES=zepto event fx fx_methods
> npm run dist

1.示例Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
var div1 = $('#foo1');
div1.animate({
"width": "300px",
"height": "300px"
},
'slow', 'ease-in-out',
function() {
console.log('div1 animate callback');
// div2.hide('slow',function(){
div2.fadeOut('slow', function() {
console.log('div2 animate callback');
});
}, '2000');

2.fx

zepto的动画采用的是CSS3的动画/过渡,未做兼容。
核心方法是$.fn.animate = function(properties, duration, ease, callback, delay),实际上的处理逻辑是$.fn.anim = function(properties, duration, ease, callback, delay)
其本质就是设置好css3属性对象,然后用this.css(cssValues)方法是css3动画起作用。浏览器不支持的动画使用setTimeout

1
2
3
4
// duration为0,即浏览器不支持动画的情况,直接执行动画结束,执行回调。
if (duration <= 0) setTimeout(function() {
that.each(function() { wrappedCallback.call(this); });
}, 0);

整体的源代码和注释放在下面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
var Fx = function($) {
var prefix = '',
eventPrefix,
vendors = { Webkit: 'webkit', Moz: '', O: 'o' },
testEl = document.createElement('div'),
supportedTransforms = /^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i,
transform,
transitionProperty, transitionDuration, transitionTiming, transitionDelay, //过渡
animationName, animationDuration, animationTiming, animationDelay, //动画
cssReset = {};

//将驼峰字符串转成css属性,如aB-->a-b
function dasherize(str) {
return str.replace(/([a-z])([A-Z])/, '$1-$2').toLowerCase();
}
//修正事件名
function normalizeEvent(name) {
return eventPrefix ? eventPrefix + name : name.toLowerCase();
}

/**
* 根据浏览器内核,设置CSS前缀,事件前缀
* 如css:-webkit- event:webkit
* 为prefix和eventPrefix赋值
*/
if (testEl.style.transform === undefined) {
$.each(vendors, function(vendor, event) {
if (testEl.style[vendor + 'TransitionProperty'] !== undefined) {
prefix = '-' + vendor.toLowerCase() + '-';
eventPrefix = event;
return false;
}
});
}

transform = prefix + 'transform';
//均为空''
cssReset[transitionProperty = prefix + 'transition-property'] =
cssReset[transitionDuration = prefix + 'transition-duration'] =
cssReset[transitionDelay = prefix + 'transition-delay'] =
cssReset[transitionTiming = prefix + 'transition-timing-function'] =
cssReset[animationName = prefix + 'animation-name'] =
cssReset[animationDuration = prefix + 'animation-duration'] =
cssReset[animationDelay = prefix + 'animation-delay'] =
cssReset[animationTiming = prefix + 'animation-timing-function'] = '';
/**
* 动画常量数据源
* @type {{off: boolean, speeds: {_default: number, fast: number, slow: number}, cssPrefix: string, transitionEnd: *, animationEnd: *}}
*/
$.fx = {
off: (eventPrefix === undefined && testEl.style.transitionProperty === undefined), //能力检测是否支持动画,具体检测是否支持过渡,支持过渡事件
speeds: { _default: 400, fast: 200, slow: 600 },
cssPrefix: prefix, //css 前缀 如-webkit-
transitionEnd: normalizeEvent('TransitionEnd'), //过渡结束事件
animationEnd: normalizeEvent('AnimationEnd') //动画播放结束事件
};
/**
* [animate 自定义动画]
* @param {[Object]} properties [属性变化成,如{"width":"300px"}]
* @param {[type]} duration [速度 如slow或者一个数字]
* @param {[type]} ease [变化的速率ease、linear、ease-in / ease-out、ease-in-out
cubic-bezier]
* @param {Function} callback [回调函数]
* @param {[type]} delay [延迟时间]
*/
$.fn.animate = function(properties, duration, ease, callback, delay) {
//参数处理
if ($.isFunction(duration)) //传参为function(properties,callback)
callback = duration, ease = undefined, duration = undefined;
if ($.isFunction(ease)) //传参为function(properties,duration,callback)
callback = ease, ease = undefined;
if ($.isPlainObject(duration)) //传参为function(properties,{})
ease = duration.easing, callback = duration.complete, delay = duration.delay, duration = duration.duration
//duration参数处理
if (duration) duration = (typeof duration == 'number' ? duration :
($.fx.speeds[duration] || $.fx.speeds._default)) / 1000;
if (delay) delay = parseFloat(delay) / 1000;
return this.anim(properties, duration, ease, callback, delay);
};
$.fn.anim = function(properties, duration, ease, callback, delay) {
var key, cssValues = {},
cssProperties, transforms = '',
that = this,
wrappedCallback, endEvent = $.fx.transitionEnd,
fired = false;
//修正好时间
if (duration === undefined) duration = $.fx.speeds._default / 1000;
if (delay === undefined) delay = 0;
if ($.fx.off) duration = 0; //如果浏览器不支持动画,持续时间设为0,直接跳动画结束

if (typeof properties == 'string') {
// keyframe animation
cssValues[animationName] = properties; //properties是动画名
cssValues[animationDuration] = duration + 's';
cssValues[animationDelay] = delay + 's';
cssValues[animationTiming] = (ease || 'linear');
endEvent = $.fx.animationEnd;
} else { //properties 是样式集对象
cssProperties = [];
// CSS transitions
for (key in properties) {
//是这些属性^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)
if (supportedTransforms.test(key)) {
transforms += key + '(' + properties[key] + ') '; //拼凑成变形方法
} else {
cssValues[key] = properties[key], cssProperties.push(dasherize(key));
}
}
if (transforms) cssValues[transform] = transforms, cssProperties.push(transform);
if (duration > 0 && typeof properties === 'object') {
cssValues[transitionProperty] = cssProperties.join(', ');
cssValues[transitionDuration] = duration + 's';
cssValues[transitionDelay] = delay + 's';
cssValues[transitionTiming] = (ease || 'linear');
}
}
//动画完成后的响应函数
wrappedCallback = function(event) {
if (typeof event !== 'undefined') {
if (event.target !== event.currentTarget) return; // makes sure the event didn't bubble from "below"
$(event.target).unbind(endEvent, wrappedCallback);
} else {
$(this).unbind(endEvent, wrappedCallback); // triggered by setTimeout
}

fired = true;
$(this).css(cssReset);

callback && callback.call(this);
};
//处理动画结束事件
if (duration > 0) {
//绑定动画结束事件
this.bind(endEvent, wrappedCallback);
// transitionEnd is not always firing on older Android phones
// so make sure it gets fired
//延时ms后执行动画,注意这里加了25ms,保持endEvent,动画先执行完。
//绑定过事件还做延时处理,是transitionEnd在older Android phones不一定触发
setTimeout(function() {
if (fired) return;
wrappedCallback.call(that);
}, ((duration + delay) * 1000) + 25);
}

// trigger page reflow so new elements can animate
//主动触发页面回流,刷新DOM,让接下来设置的动画可以正确播放
//更改 offsetTop、offsetLeft、 offsetWidth、offsetHeight;scrollTop、scrollLeft、
//scrollWidth、scrollHeight;clientTop、clientLeft、clientWidth、clientHeight;getComputedStyle() 、
//currentStyle()。这些都会触发回流。回流导致DOM重新渲染,平时要尽可能避免,
//但这里,为了动画即时生效播放,则主动触发回流,刷新DOM

this.size() && this.get(0).clientLeft;

//设置样式,启动动画
this.css(cssValues);

// duration为0,即浏览器不支持动画的情况,直接执行动画结束,执行回调。
if (duration <= 0) setTimeout(function() {
that.each(function() { wrappedCallback.call(this); });
}, 0);

return this;
};

testEl = null;
};
export default Fx;

3.fx_methods

源码中的fx_methods(fx_methods.js中)方法,说白了就是利用上面的fx.js文件下的$.fn.animate函数提供便捷的方法

整体源码和注释放在这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
var Fx_methods = function($) {
var document = window.document,
docElem = document.documentElement,
origShow = $.fn.show,
origHide = $.fn.hide,
origToggle = $.fn.toggle;

function anim(el, speed, opacity, scale, callback) {
if (typeof speed == 'function' && !callback) callback = speed, speed = undefined;
var props = { opacity: opacity };
if (scale) {
props.scale = scale;
el.css($.fx.cssPrefix + 'transform-origin', '0 0'); //设置变形原点
}
return el.animate(props, speed, null, callback); //不支持速率变化
}

function hide(el, speed, scale, callback) {
//$(dom).animate({opacity: 0, '-webkit-transform-origin': '0px 0px 0px', '-webkit-transform': 'scale(0, 0)' },800)
//设置了变形原点,缩放为0,它就会缩到左上角再透明
return anim(el, speed, 0, scale, function() {
origHide.call($(this));
callback && callback.call(this);
});
}

$.fn.show = function(speed, callback) {
origShow.call(this);
if (speed === undefined) {
speed = 0;
} else {
this.css('opacity', 0);
}
return anim(this, speed, 1, '1,1', callback);
};

$.fn.hide = function(speed, callback) {
if (speed === undefined) {
return origHide.call(this);
} else {
return hide(this, speed, '0,0', callback);
}
};

$.fn.toggle = function(speed, callback) {
if (speed === undefined || typeof speed == 'boolean') {
return origToggle.call(this, speed);
} else {
return this.each(function() {
var el = $(this);
el[el.css('display') == 'none' ? 'show' : 'hide'](speed, callback);
});

}
};

$.fn.fadeTo = function(speed, opacity, callback) {
return anim(this, speed, opacity, null, callback);
};

$.fn.fadeIn = function(speed, callback) {
var target = this.css('opacity');
if (target > 0){
this.css('opacity', 0);
}
else {
target = 1;
}
return origShow.call(this).fadeTo(speed, target, callback);
};

$.fn.fadeOut = function(speed, callback) {
return hide(this, speed, null, callback);
};

$.fn.fadeToggle = function(speed, callback) {
return this.each(function() {
var el = $(this);
el[
(el.css('opacity') == 0 || el.css('display') == 'none') ? 'fadeIn' : 'fadeOut'
](speed, callback);
});
};
};

export default Fx_methods;

全部代码挂在我的github上,本博文对应文件夹v0.7.x。
https://github.com/zrysmt/DIY-zepto

参考阅读: